package fusion

import (
	"database/sql"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"reflect"
	"strconv"
	"strings"
)

func LoadTableFromDB(db *sql.DB, t reflect.Type, where string, args ...interface{}) (reflect.Value, error) {
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	if t.Kind() != reflect.Struct {
		panic(fmt.Errorf("LoadTableFromDB(%s): Type isn't struct", t.Name()))
	}
	n := t.NumField()
	fields := make([]*reflect.StructField, 0, n)
	fieldNames := make([]string, 0, n)
	for i := 0; i < n; i++ {
		field := t.Field(i)
		tagDB := field.Tag.Get("db")
		if tagDB != "-" {
			if tagDB != "" {
				fieldNames = append(fieldNames, tagDB)
			} else {
				fieldNames = append(fieldNames, field.Name)
			}
			fields = append(fields, &field)
		}
	}
	tableName := reflect.Zero(reflect.PtrTo(t)).Interface().(IGetTableName).GetTableName()
	querySQL := fmt.Sprintf("SELECT `%s` FROM `%s`", strings.Join(fieldNames, "`,`"), tableName)
	if where != "" {
		querySQL = fmt.Sprintf("%s WHERE %s", querySQL, where)
	}
	rows, err := db.Query(querySQL, args...)
	if err != nil {
		return reflect.Value{}, fmt.Errorf("LoadTableFromDB(%s) -> %s", tableName, err)
	}
	defer rows.Close()
	fieldVals := make([]interface{}, len(fields))
	vals := reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(t)), 0, 0)
	for rows.Next() {
		v := reflect.New(t)
		for i, field := range fields {
			fieldVals[i] = v.Elem().FieldByIndex(field.Index).Addr().Interface()
		}
		if err = rows.Scan(fieldVals...); err != nil {
			return vals, fmt.Errorf("LoadTableFromDB(%s) -> %s", tableName, err)
		}
		vals = reflect.Append(vals, v)
	}
	return vals, nil
}

func LoadJsontableFromDB(db *sql.DB, t reflect.Type, where string, args ...interface{}) (reflect.Value, error) {
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	if t.Kind() != reflect.Struct {
		panic(fmt.Errorf("LoadJsontableFromDB(%s): Type isn't struct", t.Name()))
	}
	n := t.NumField()
	requireds := make([]*reflect.StructField, 0, n)
	optionals := make([]*reflect.StructField, 0, n)
	for i := 0; i < n; i++ {
		field := t.Field(i)
		tagDB := field.Tag.Get("rule")
		if tagDB == "required" {
			requireds = append(requireds, &field)
		} else {
			optionals = append(optionals, &field)
		}
	}
	tableName := reflect.Zero(reflect.PtrTo(t)).Interface().(IGetTableName).GetTableName()
	querySQL := fmt.Sprintf("SELECT * FROM `%s`", tableName)
	if where != "" {
		querySQL = fmt.Sprintf("%s WHERE %s", querySQL, where)
	}
	rows, err := db.Query(querySQL, args...)
	if err != nil {
		return reflect.Value{}, fmt.Errorf("LoadJsontableFromDB(%s) -> %s", tableName, err)
	}
	defer rows.Close()
	fieldNums := len(requireds) + IfInt(len(optionals) > 0, 1, 0)
	fieldRawVals := make([]sql.NullString, fieldNums)
	fieldScanVals := make([]interface{}, fieldNums)
	for i := 0; i < fieldNums; i++ {
		fieldScanVals[i] = &fieldRawVals[i]
	}
	vals := reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(t)), 0, 0)
	for rows.Next() {
		v := reflect.New(t)
		if err = rows.Scan(fieldScanVals...); err != nil {
			return vals, fmt.Errorf("LoadJsontableFromDB(%s) -> %s", tableName, err)
		}
		if len(optionals) > 0 {
			fieldRawVal := fieldRawVals[fieldNums-1]
			if fieldRawVal.Valid {
				json.Unmarshal([]byte(fieldRawVal.String), v.Interface())
			}
		}
		for i, fieldRawVal := range fieldRawVals[:len(requireds)] {
			if fieldRawVal.Valid {
				LoadJsontableFieldValue(v, requireds[i].Index, fieldRawVal.String)
			}
		}
		vals = reflect.Append(vals, v)
	}
	return vals, nil
}

func LoadJsontableFieldValue(v reflect.Value, i []int, s string) {
	var field = v.Elem().FieldByIndex(i)
	switch field.Kind() {
	case reflect.Bool:
		x, _ := strconv.ParseBool(s)
		field.SetBool(x)
	case reflect.Float32, reflect.Float64:
		x, _ := strconv.ParseFloat(s, 64)
		field.SetFloat(x)
	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		x, _ := strconv.ParseInt(s, 0, 64)
		field.SetInt(x)
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		x, _ := strconv.ParseUint(s, 0, 64)
		field.SetUint(x)
	case reflect.String:
		field.SetString(s)
	case reflect.Slice, reflect.Map, reflect.Struct:
		json.Unmarshal([]byte(s), field.Addr().Interface())
	default:
		panic(errors.New("invalid type " + field.Type().String()))
	}
}

func SaveJsontableToDB(tx *sql.Tx, value interface{}) (uint32, error) {
	t, v := reflect.TypeOf(value), reflect.ValueOf(value)
	if t.Kind() == reflect.Ptr {
		t, v = t.Elem(), v.Elem()
	}
	if t.Kind() != reflect.Struct {
		panic(fmt.Errorf("SaveJsontableToDB(%s): Type isn't struct", t.Name()))
	}
	n := t.NumField()
	requireds := make([]*reflect.StructField, 0, n)
	optionals := make([]*reflect.StructField, 0, n)
	for i := 0; i < n; i++ {
		field := t.Field(i)
		tagDB := field.Tag.Get("rule")
		if tagDB == "required" {
			requireds = append(requireds, &field)
		} else {
			optionals = append(optionals, &field)
		}
	}
	params := make([]interface{}, 0, len(requireds)+IfInt(len(optionals) > 0, 1, 0))
	for _, field := range requireds {
		params = append(params, SaveJsontableFieldValue(v, field.Index))
	}
	if len(optionals) > 0 {
		optional := reflect.New(t)
		for _, field := range optionals {
			optional.Elem().FieldByIndex(field.Index).Set(v.FieldByIndex(field.Index))
		}
		params = append(params, Check(json.Marshal(optional.Interface()))[0])
	}
	tableName := reflect.Zero(reflect.PtrTo(t)).Interface().(IGetTableName).GetTableName()
	strSQL := fmt.Sprintf(
		"REPLACE INTO `%s` VALUES(%s)", tableName, strings.Repeat(",?", len(params))[1:])
	rst, err := tx.Exec(strSQL, params...)
	if err != nil {
		return 0, fmt.Errorf("SaveJsontableToDB(%s) -> %s", tableName, err)
	}
	ID, err := rst.LastInsertId()
	if err != nil {
		return 0, fmt.Errorf("SaveJsontableToDB(%s) -> %s", tableName, err)
	}
	return uint32(ID), nil
}

func SaveJsontableFieldValue(v reflect.Value, i []int) interface{} {
	var field = v.FieldByIndex(i)
	switch field.Kind() {
	case reflect.Bool:
		fallthrough
	case reflect.Float32, reflect.Float64:
		fallthrough
	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		fallthrough
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		fallthrough
	case reflect.String:
		return field.Interface()
	case reflect.Slice, reflect.Map, reflect.Struct:
		return Check(json.Marshal(field.Interface()))[0]
	default:
		panic(errors.New("invalid type " + field.Type().String()))
	}
}

func MarshalEntityToWriter(value interface{}, w io.Writer) error {
	t, v := reflect.TypeOf(value), reflect.ValueOf(value)
	if t.Kind() == reflect.Ptr {
		t, v = t.Elem(), v.Elem()
	}
	switch t.Kind() {
	case reflect.Bool:
		fallthrough
	case reflect.Float32, reflect.Float64:
		fallthrough
	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		fallthrough
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return binary.Write(w, defByteOrder, value)
	case reflect.String:
		x := v.String()
		if err := ToWriter7BitEncodedInt(uint(len(x)), w); err != nil {
			return err
		}
		if _, err := w.Write([]byte(x)); err != nil {
			return err
		}
	case reflect.Slice:
		n := v.Len()
		if err := ToWriter7BitEncodedInt(uint(n), w); err != nil {
			return err
		}
		for i := 0; i < n; i++ {
			if err := MarshalEntityToWriter(v.Index(i).Interface(), w); err != nil {
				return err
			}
		}
	case reflect.Map:
		n := v.Len()
		if err := ToWriter7BitEncodedInt(uint(n), w); err != nil {
			return err
		}
		for itr := v.MapRange(); itr.Next(); {
			if err := MarshalEntityToWriter(itr.Key().Interface(), w); err != nil {
				return err
			}
			if err := MarshalEntityToWriter(itr.Value().Interface(), w); err != nil {
				return err
			}
		}
	case reflect.Struct:
		n := v.NumField()
		for i := 0; i < n; i++ {
			if err := MarshalEntityToWriter(v.Field(i).Interface(), w); err != nil {
				return err
			}
		}
	default:
		panic(errors.New("invalid type " + t.String()))
	}
	return nil
}

func UnmarshalEntityFromReader(value interface{}, r io.Reader) error {
	t, v := reflect.TypeOf(value), reflect.ValueOf(value)
	if t.Kind() == reflect.Ptr {
		t, v = t.Elem(), v.Elem()
	} else {
		panic(errors.New("invalid type " + t.String()))
	}
	switch t.Kind() {
	case reflect.Bool:
		fallthrough
	case reflect.Float32, reflect.Float64:
		fallthrough
	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		fallthrough
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return binary.Read(r, defByteOrder, value)
	case reflect.String:
		var n uint
		var err error
		if n, err = FromReader7BitEncodedInt(r); err != nil {
			return err
		}
		x := make([]byte, n)
		if _, err := io.ReadFull(r, x); err != nil {
			return err
		}
		v.SetString(string(x))
	case reflect.Slice:
		var n uint
		var err error
		if n, err = FromReader7BitEncodedInt(r); err != nil {
			return err
		}
		x := reflect.MakeSlice(t, int(n), 0)
		for i := 0; i < int(n); i++ {
			if err := UnmarshalEntityFromReader(x.Index(i).Addr().Interface(), r); err != nil {
				return err
			}
		}
		v.Set(x)
	case reflect.Map:
		var n uint
		var err error
		if n, err = FromReader7BitEncodedInt(r); err != nil {
			return err
		}
		x := reflect.MakeMapWithSize(t, int(n))
		for i := 0; i < int(n); i++ {
			key, val := reflect.New(t.Key()), reflect.New(t.Elem())
			if err := UnmarshalEntityFromReader(key.Interface(), r); err != nil {
				return err
			}
			if err := UnmarshalEntityFromReader(val.Interface(), r); err != nil {
				return err
			}
			x.SetMapIndex(key, val)
		}
		v.Set(x)
	case reflect.Struct:
		n := v.NumField()
		for i := 0; i < n; i++ {
			if err := UnmarshalEntityFromReader(v.Field(i).Addr().Interface(), r); err != nil {
				return err
			}
		}
	default:
		panic(errors.New("invalid type " + t.String()))
	}
	return nil
}

func MarshalEntityToInterface(value interface{}, tag string) interface{} {
	t, v := reflect.TypeOf(value), reflect.ValueOf(value)
	if t.Kind() == reflect.Ptr {
		if !v.IsNil() {
			t, v = t.Elem(), v.Elem()
		} else {
			return nil
		}
	}
	switch t.Kind() {
	case reflect.Bool:
		fallthrough
	case reflect.Float32, reflect.Float64:
		fallthrough
	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		fallthrough
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		fallthrough
	case reflect.String:
		return value
	case reflect.Slice:
		n := v.Len()
		vals := make([]interface{}, n)
		for i := 0; i < n; i++ {
			vals[i] = MarshalEntityToInterface(v.Index(i).Interface(), tag)
		}
		return vals
	case reflect.Map:
		n := v.Len()
		vals := make(map[string]interface{}, n)
		for itr := v.MapRange(); itr.Next(); {
			vals[itr.Key().String()] =
				MarshalEntityToInterface(itr.Value().Interface(), tag)
		}
		return vals
	case reflect.Struct:
		n := v.NumField()
		vals := make(map[string]interface{}, n)
		for i := 0; i < n; i++ {
			fieldVal := MarshalEntityToInterface(v.Field(i).Interface(), tag)
			if field := t.Field(i); !field.Anonymous {
				vals[GetStructFieldTagName(&field, tag)] = fieldVal
			} else {
				switch rst := fieldVal.(type) {
				case map[string]interface{}:
					for k, v := range rst {
						vals[k] = v
					}
				}
			}
		}
		return vals
	default:
		panic(errors.New("invalid type " + t.String()))
	}
}

func GetStructFieldTagName(field *reflect.StructField, tag string) string {
	if tag != "" && field.Tag != "" {
		if tagVal := field.Tag.Get(tag); tagVal != "" {
			if tagName := strings.SplitN(tagVal, ",", 2)[0]; tagName != "" {
				return tagName
			}
		}
	}
	return field.Name
}
